package uebung02_w;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;

public class SIFTDescriptor extends AffineTransform implements Shape {
	
	Path2D path = new Path2D.Double();
	Shape bounds; 
	Point2D center = new Point2D.Double();
	double[] values = new double[128];
	final double maxDist, width, histogramNorm; 
	AffineTransform transform = new AffineTransform();
	
	public SIFTDescriptor(AffineTransform at) {
		
		this.transform.setTransform(at);
		this.center.setLocation(at.getTranslateX(), at.getTranslateY());
		this.bounds = this.transform.createTransformedShape(new Rectangle2D.Double(-8,-8,16,16));
		this.path.append(this.bounds, false);
		this.path.append(this.transform.createTransformedShape(new Ellipse2D.Double(-8,-8,16,16)), false);
		
		Point2D center = new Point2D.Double();
		at.transform(center, center);

		Point2D dist = new Point2D.Double(-8,-8);
		at.transform(dist, dist);
		
		this.maxDist = dist.distance(center);
		this.width = 2*Math.abs(dist.getX()-center.getX());

		dist.setLocation(-2, -2);
		at.transform(dist, dist);
		
		this.histogramNorm = dist.distance(center);
		this.path.lineTo(center.getX(), center.getY());
	}
	public int size() {
		return 128;
	}
	public double get(int index) {
		return this.values[index];
	}
	boolean dirty = true;
	double max = 0;
	
	public double getMax() {
		if (dirty) {
			max = 0;
			for (int i = 0; i<this.values.length; i++)
				max = Math.max(max, values[i]);
			dirty = false;
		}
		return max;
	}
	public void capAndNormalize() {
		for (int i = 0; i<values.length; i++) {
			values[i] = Math.min(0.2, values[i]);
		}
		this.normalize();
	}

	public double getHistogramNorm() {
		return this.histogramNorm;
	}
	public double getMaxDist() {
		return this.maxDist;
	}
	public AffineTransform getTransform() {
		return this.transform;
	}
	public double getWidth() {
		return this.width;
	}
	public void normalize() {
		double sumSq = 0;
		for (int i = 0; i<this.values.length; i++) {
			sumSq += this.values[i]*this.values[i];
		}
		double norm = Math.sqrt(sumSq);
		for (int i = 0; i<this.values.length; i++) {
			this.values[i] /= norm;
		}
		dirty = true;
	}
	public void add(int index, double value) {
		this.values[index] += value;
		dirty = true;
//		System.out.println(values[index]);
	}
	public Point2D getCenter() {
		return this.center;
	}
	public static void main(String[] args) {
		try {
			final BufferedImage kermit = ImageIO.read(Thread.currentThread().getContextClassLoader().getResource("res/kermit001.jpg"));
			
			JFrame ff = new JFrame();
			ff.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			
			
			final AffineTransform siftTransform = new AffineTransform().getTranslateInstance(kermit.getWidth()/2+0.25, kermit.getHeight()/2+0.125), zoom = new AffineTransform();
			siftTransform.concatenate(AffineTransform.getRotateInstance(Math.toRadians(13)));
			
			final SIFTDescriptor sd = new SIFTDescriptor(siftTransform), su = new SIFTDescriptor(AffineTransform.getTranslateInstance(kermit.getWidth()/2, kermit.getHeight()/2));
			
			final JComponent canvas = new JComponent() {
				{setPreferredSize(new Dimension(kermit.getWidth(), kermit.getHeight()));}
				@Override
				public void paint(Graphics g) {
					Graphics2D g2 = (Graphics2D)g;
					g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				    g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
				    g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				    
					g2.transform(zoom);
					g2.drawImage(kermit, 0, 0, this);
					g2.setColor(Color.MAGENTA);
					g2.draw(su);
					g2.setColor(Color.GREEN);
					g2.draw(sd);
					super.paint(g);
				}
			};
			canvas.addMouseWheelListener(new MouseWheelListener() {
				
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					
					double scale = Math.pow(1.01, e.getWheelRotation());

					try {
						Point2D p = new Point2D.Double(e.getX(), e.getY()); 
						zoom.inverseTransform(p, p);
						zoom.concatenate(AffineTransform.getTranslateInstance(p.getX(), p.getY()));
						zoom.concatenate(AffineTransform.getScaleInstance(scale, scale));
						zoom.concatenate(AffineTransform.getTranslateInstance(-p.getX(), -p.getY()));
					}
					catch (NoninvertibleTransformException nte ) {
						nte.printStackTrace();
					}
					canvas.repaint();
				}
			});
			ff.add(canvas);
			
			ff.pack();
			ff.setVisible(true);
			
		}
		catch (IOException iox) {
			iox.printStackTrace();
		}
	}
	@Override
	public boolean contains(Point2D p) {
		return this.bounds.contains(p);
	}
	@Override
	public boolean contains(Rectangle2D r) {
		return this.bounds.contains(r);
	}
	@Override
	public boolean contains(double x, double y) {
		return this.bounds.contains(x, y);
	}
	@Override
	public boolean contains(double x, double y, double w, double h) {
		return this.bounds.contains(x, y, w, h);
	}
	@Override
	public Rectangle getBounds() {
		return this.path.getBounds();
	}
	@Override
	public Rectangle2D getBounds2D() {
		return this.path.getBounds2D();
	}
	@Override
	public PathIterator getPathIterator(AffineTransform at) {
		return this.path.getPathIterator(at);
	}
	@Override
	public PathIterator getPathIterator(AffineTransform at, double flatness) {
		return this.path.getPathIterator(at, flatness);
	}
	@Override
	public boolean intersects(Rectangle2D r) {
		return this.path.intersects(r);
	}
	@Override
	public boolean intersects(double x, double y, double w, double h) {
		return this.path.intersects(x, y, w, h);
	}
	@Override
	public String toString() {
		return "SIFT Deskritor@"+this.transform.toString()+Arrays.toString(this.values);
	}
	
}
